# ~*~ coding: utf-8 ~*~
import time
import os
import sshpubkeys
import uuid
from hashlib import md5

from flask import current_app
from apps.common.dbbase import BaseModel
from apps.common.dbtypes import EncryptCharField, EncryptTextField
from apps.common.utils import generate_uuid, get_signer
from apps.extensions import db
from ..utils.genkey import ssh_key_string_to_obj, ssh_key_gen
from ..utils.connectivity import Connectivity

signer = get_signer()


__all__ = ['AdminUser', 'SystemUser']


class AssetUser(BaseModel):
    __abstract__ = True

    id = db.Column(db.CHAR(36), default=generate_uuid, primary_key=True)
    name = db.Column(db.VARCHAR(128), nullable=False, unique=True)
    username = db.Column(db.VARCHAR(32), nullable=True)
    password = db.Column(EncryptCharField(256), nullable=True)
    private_key = db.Column(EncryptTextField, nullable=True)
    public_key = db.Column(EncryptTextField, nullable=True)

    create_time = db.Column(db.INT, default=time.time)
    update_time = db.Column(db.INT, default=time.time, onupdate=time.time)

    @property
    def private_key_obj(self):
        if self.private_key:
            return ssh_key_string_to_obj(self.private_key, password=self.password)
        else:
            return None

    @property
    def private_key_file(self):
        if not self.private_key_obj:
            return None

        key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest()
        key_path = os.path.join(current_app.config.get('TEMP_DIR'), key_name)
        if not os.path.exists(key_path):
            self.private_key_obj.write_private_key_file(key_path)
            os.chmod(key_path, 0o400)
        return key_path

    @property
    def public_key_obj(self):
        if self.public_key:
            try:
                return sshpubkeys.SSHKey(self.public_key)
            except TabError:
                pass
        return None

    def set_auth(self, password=None, private_key=None, public_key=None):
        self.password = password
        self.private_key = private_key
        self.public_key = public_key

    @property
    def auth_info(self):
        return {
            'username': self.username,
            'password': self.password,
            'private_key': self.private_key_file,
        }

    @staticmethod
    def gen_auth(username):
        password = str(uuid.uuid4())
        private_key, public_key = ssh_key_gen(
            username=username
        )
        return password, private_key, public_key

    def auto_gen_auth(self):
        password = str(uuid.uuid4())
        private_key, public_key = ssh_key_gen(
            username=self.username
        )
        self.set_auth(
            password=password,
            private_key=private_key,
            public_key=public_key
        )

    CONNECTIVITY_ASSET_CACHE_KEY = "ASSET_USER_{}_{}_ASSET_CONNECTIVITY"

    @property
    def connectivity(self):
        assets = self.get_related_assets()
        if not isinstance(assets, list):
            assets = assets.only('id', 'hostname', 'admin_user__id')
        data = {
            'unreachable': [],
            'reachable': [],
            'unknown': [],
        }
        for asset in assets:
            connectivity = self.get_asset_connectivity(asset)
            if connectivity.is_reachable():
                data["reachable"].append(asset.hostname)
            elif connectivity.is_unreachable():
                data["unreachable"].append(asset.hostname)
            else:
                data["unknown"].append(asset.hostname)
        return data

    def get_asset_connectivity_key(self, asset):
        return self.CONNECTIVITY_ASSET_CACHE_KEY.format(self.username, asset.id)

    def get_asset_connectivity(self, asset):
        key = self.get_asset_connectivity_key(asset)
        return Connectivity.get(key)

    def set_asset_connectivity(self, asset, c):
        key = self.get_asset_connectivity_key(asset)
        Connectivity.set(key, c)


class AdminUser(AssetUser):
    """
    一个特权用户，可以使用它来推送系统用户。
    """
    __tablename__ = 'assets_adminuser'

    become = db.Column(db.BOOLEAN, default=True)
    become_method = db.Column(db.VARCHAR(4), default='sudo')
    become_user = db.Column(db.VARCHAR(32), default='root')
    _become_pass = db.Column(db.VARCHAR(128), default='')

    @property
    def become_pass(self):
        password = signer.unsign(self._become_pass)
        if password:
            return password
        else:
            return ""

    @become_pass.setter
    def become_pass(self, password):
        self._become_pass = signer.sign(password)

    @property
    def become_info(self):
        if self.become:
            info = {
                "method": self.become_method,
                "user": self.become_user,
                "pass": self.become_pass,
            }
        else:
            info = None
        return info

    def __str__(self):
        return "<AdminUser: {}>".format(self.name)

    @property
    def login_mode_display(self):
        # 密码登录
        if self.password and not self.private_key:
            return 1
        # 秘钥登录
        elif not self.password and self.private_key:
            return 2
        # 密码和秘钥登录
        elif self.password and self.private_key:
            return 3


class SystemUser(AssetUser):
    __tablename__ = 'assets_systemuser'

    LOGIN_AUTO = 'auto'
    LOGIN_MANUAL = 'manual'

    # 如果选择手动登录模式，用户名和密码可以不填写
    login_mode = db.Column(db.VARCHAR(10), default=LOGIN_AUTO)
    # 1-100, 1最低优先级，100最高优先级。授权多个用户时，高优先级的系统用户将会作为默认登录用户
    priority = db.Column(db.INT, default=20)
    # 自动推送
    auto_push = db.Column(db.BOOLEAN, default=True)
    # sudo权限
    sudo = db.Column(db.TEXT, default='/bin/whoami')
    # 登录shell
    shell = db.Column(db.VARCHAR(64), default='/bin/bash')

    def is_need_push(self):
        if self.auto_push:
            return True
        else:
            return False

    @property
    def login_mode_display(self):
        # 手动登录
        if self.login_mode == SystemUser.LOGIN_MANUAL:
            return 1
        elif self.login_mode == SystemUser.LOGIN_AUTO:
            # 自动登录 - 密码登录
            if self.password and not self.private_key:
                return 2

            # 自动登录 - 秘钥登录
            if not self.password and self.private_key:
                return 3

            # 自动登录 - 密码和秘钥登录
            if self.password and self.private_key:
                return 4

    def __str__(self):
        return "<SystemUser: {}>".format(self.name)
